#include "ObjLoader.h"
#include <stdlib.h>
#include <string>
#include "fmtxlib.h"

using namespace std;
using namespace fmath;

ObjFace::ObjFace()
{
	m_size = 0;
	memset(m_vertInd, 0, 4*sizeof(short));
	memset(m_normInd, 0, 4*sizeof(short));
}

ObjFace::ObjFace(unsigned long v1, unsigned long v2, unsigned long v3,
		unsigned long n1, unsigned long n2, unsigned long n3)
{
	m_size = 3;
	m_vertInd[0] = v1;
	m_vertInd[1] = v2;
	m_vertInd[2] = v3;
	m_normInd[0] = n1;
	m_normInd[1] = n2;
	m_normInd[2] = n3;
}

ObjFace::ObjFace(unsigned long v1, unsigned long v2, 
		unsigned long v3, unsigned long v4, 
		unsigned long n1, unsigned long n2,
		unsigned long n3, unsigned long n4)
{
	m_size = 4;
	m_vertInd[0] = v1;
	m_vertInd[1] = v2;
	m_vertInd[2] = v3;
	m_vertInd[3] = v4;
	m_normInd[0] = n1;
	m_normInd[1] = n2;
	m_normInd[2] = n3;
	m_normInd[3] = n4;
}



int ObjLoader::Load(const wchar_t * a_filename, std::list<Mesh*> * a_meshes)
{
	if(a_filename == NULL || a_meshes == NULL) return BAD_ARG;
	int res;
	Mesh* mesh;
	fstream file;
	file.open(a_filename, ios::in);
	if( !file.is_open() )
		return CANT_OPEN_FILE;

	mesh = new Mesh();
	res = ReadFile(file, mesh);
	if( mesh->m_vertices.size() != 0 )
		a_meshes->push_back(mesh);
	file.close();
	return res;
}

void ObjLoader::FaceInterprer(const std::string& a_line, ObjFace& o_f)
{
	int count;
	int v[4]={0, 0, 0, 0};
	int n[4]={0, 0, 0, 0};

	if(m_vtP && m_vnP) {
		count = sscanf_s(a_line.c_str(), 
				"%*c %d/%*d/%d %d/%*d/%d %d/%*d/%d %d/%*d/%d",
				v, n, v+1, n+1, v+2, n+2, v+3, n+3);
		}
		else if(m_vtP)
			count = 
				sscanf_s(a_line.c_str(), 
				"%*c %d/%*d %d/%*d %d/%*d %d/%*d",
				v, v+1, v+2, v+3);
		else if(m_vnP)
			count = 
				sscanf_s(a_line.c_str(), 
				"%*c %d//%d %d//%d %d//%d %d//%d",
				v, n, v+1, n+1, v+2, n+2, v+3, n+3);
		else
			count = 
				sscanf_s(a_line.c_str(), 
				"%*c %d %d %d %d",
				v, v+1, v+2, v+3);

		o_f.m_vertInd[0] = static_cast<unsigned long>(v[0]);
		o_f.m_vertInd[1] = static_cast<unsigned long>(v[1]);
		o_f.m_vertInd[2] = static_cast<unsigned long>(v[2]);
		o_f.m_vertInd[3] = static_cast<unsigned long>(v[3]);
		o_f.m_normInd[0] = static_cast<unsigned long>(n[0]);
		o_f.m_normInd[1] = static_cast<unsigned long>(n[1]);
		o_f.m_normInd[2] = static_cast<unsigned long>(n[2]);
		o_f.m_normInd[3] = static_cast<unsigned long>(n[3]);

		if(count == 3 || count == 6) {
			o_f.m_size = 3;
			++m_trCounter;
		}
		else if(count == 4 || count == 8) {
			o_f.m_size = 4;
			m_trCounter += 2;
		}
}

void ObjLoader::LineInterpreter(const std::string& a_line,
								std::list<Vertex>& o_vert, 
								std::list<Normal>& o_norm,
								std::list<ObjFace>& o_face)
{
	vector3 vec;
	ObjFace f;
	if(a_line.empty()) return;
	if(a_line[0] == 'v' && a_line[1] == 't' && !m_vtP)
		m_vtP = true;
	if(a_line[0] == 'v' && a_line[1] == 'n')
	{
		m_vnP = true;
		if( 3 == sscanf_s(a_line.c_str(), "%*s %f %f %f", &vec.x, &vec.y, &vec.z) )
			o_norm.push_back( Normal(vec, 0) );
	} else if( a_line[0] == 'v' && a_line[1] == ' ') {
		if( 3 == sscanf_s(a_line.c_str(), "%*s %f %f %f", &vec.x, &vec.y, &vec.z) )
			o_vert.push_back( Vertex(vec) );
	} else if(a_line[0] == 'f') {

		FaceInterprer(a_line, f);
		if(f.m_size >= 3)
			o_face.push_back(f);
	}
}

int ObjLoader::ReadFile(std::fstream& a_file, Mesh *o_mesh)
{
	list<Vertex> vertex;
	list<Normal> normals;
	list<ObjFace> face;
	string line;
	m_vnP = m_vtP = false;
	m_trCounter = 0;
	while( !a_file.eof() ) {
		//a_file >> line;
		std::getline(a_file, line);
		LineInterpreter(line, vertex, normals, face);
		line.clear();
	}
	
	if(m_vnP && normals.size()) CreateMeshHavingNormals(vertex, normals, face, o_mesh);
	else CreateMeshWithOutNormals(vertex, normals, face, o_mesh);
	return ALL_OK;
}

void ObjLoader::CreateMeshHavingNormals(std::list<Vertex>& a_vert, 
										std::list<Normal>& a_norm, 
										std::list<ObjFace>& a_face,
										Mesh* o_mesh)
{
	Triangle tr;
	unsigned long ind;
	o_mesh->m_vertices.assign(a_vert.begin(), a_vert.end());
	o_mesh->m_normals.assign(a_norm.begin(), a_norm.end());

	list<ObjFace>::iterator it = a_face.begin();
	while( it != a_face.end() ) {
		if(it->m_vertInd[0] != o_mesh->m_normals[it->m_normInd[0]-1].m_vertexNum
			&& 0 != o_mesh->m_normals[it->m_normInd[0]-1].m_vertexNum) {
				o_mesh->m_normals.push_back( Normal(o_mesh->m_normals[it->m_normInd[0]-1].m_normal, it->m_vertInd[0]) );
				it->m_normInd[0] = static_cast<unsigned long>( o_mesh->m_normals.size() );
		}
		tr.m_normals[0] = it->m_normInd[0]-1;
		o_mesh->m_normals[tr.m_normals[0]].m_vertexNum = it->m_vertInd[0];
		if(it->m_vertInd[1] != o_mesh->m_normals[it->m_normInd[1]-1].m_vertexNum
			&& 0 != o_mesh->m_normals[it->m_normInd[1]-1].m_vertexNum) {
				o_mesh->m_normals.push_back( Normal(o_mesh->m_normals[it->m_normInd[1]-1].m_normal, it->m_vertInd[1]) );
				it->m_normInd[1] = static_cast<unsigned long>( o_mesh->m_normals.size() );
		}
		tr.m_normals[1] = it->m_normInd[1]-1;
		o_mesh->m_normals[tr.m_normals[1]].m_vertexNum = it->m_vertInd[1];
		if(it->m_vertInd[2] != o_mesh->m_normals[it->m_normInd[2]-1].m_vertexNum
			&& 0 != o_mesh->m_normals[it->m_normInd[2]-1].m_vertexNum) {
				o_mesh->m_normals.push_back( Normal(o_mesh->m_normals[it->m_normInd[2]-1].m_normal, it->m_vertInd[2]) );
				it->m_normInd[2] = static_cast<unsigned long>( o_mesh->m_normals.size() );
		}
		tr.m_normals[2] = it->m_normInd[2]-1;
		o_mesh->m_normals[tr.m_normals[2]].m_vertexNum = it->m_vertInd[2];
		o_mesh->m_triangles.push_back(tr);

		if(it->m_size == 4) {
			if(it->m_vertInd[3] != o_mesh->m_normals[it->m_normInd[3]-1].m_vertexNum
				&& 0 != o_mesh->m_normals[it->m_normInd[3]-1].m_vertexNum) {
				o_mesh->m_normals.push_back( Normal(o_mesh->m_normals[it->m_normInd[3]-1].m_normal, it->m_vertInd[3]) );
				it->m_normInd[3] = static_cast<unsigned long>( o_mesh->m_normals.size() );
			}
			tr.m_normals[0] = it->m_normInd[0]-1;
			tr.m_normals[1] = it->m_normInd[2]-1;
			tr.m_normals[2] = it->m_normInd[3]-1;
			o_mesh->m_normals[tr.m_normals[2]].m_vertexNum = it->m_vertInd[3];
			o_mesh->m_triangles.push_back(tr);
		}
		++it;
	}
	for(ind =0; ind < o_mesh->m_vertices.size(); ++ind) {
		o_mesh->m_bbox.EnlargeBBox(
			o_mesh->m_vertices[ind].m_position);
	}
	for(ind =0; ind < o_mesh->m_normals.size(); ++ind) {
		o_mesh->m_normals[ind].m_vertexNum -= 1;
	}
}

void ObjLoader::CreateMeshWithOutNormals(std::list<Vertex>& a_vert, 
										 std::list<Normal>& a_norm, 
										 std::list<ObjFace>& a_face,
										 Mesh* o_mesh)
{
	Triangle tr;
	unsigned long ind;
	o_mesh->m_vertices.assign(a_vert.begin(), a_vert.end());
	o_mesh->m_normals.resize(a_vert.size());

	list<ObjFace>::iterator it = a_face.begin();
	while( it != a_face.end() ) {
		
		tr.m_normals[0] = it->m_vertInd[0]-1;
		o_mesh->m_normals[tr.m_normals[0]].m_vertexNum = it->m_vertInd[0]-1;
		tr.m_normals[1] = it->m_vertInd[1]-1;
		o_mesh->m_normals[tr.m_normals[1]].m_vertexNum = it->m_vertInd[1]-1;
		tr.m_normals[2] = it->m_vertInd[2]-1;
		o_mesh->m_normals[tr.m_normals[2]].m_vertexNum = it->m_vertInd[2]-1;
		o_mesh->m_triangles.push_back(tr);

		if(it->m_size == 4) {			
			tr.m_normals[0] = it->m_vertInd[0]-1;
			tr.m_normals[1] = it->m_vertInd[2]-1;
			tr.m_normals[2] = it->m_vertInd[3]-1;
			o_mesh->m_normals[tr.m_normals[2]].m_vertexNum = it->m_vertInd[3]-1;
			o_mesh->m_triangles.push_back(tr);
		}
		++it;
	}
	for(ind =0; ind < o_mesh->m_vertices.size(); ++ind) {
		o_mesh->m_bbox.EnlargeBBox(
			o_mesh->m_vertices[ind].m_position);
	}
	ComputeNormalInMesh(o_mesh);
}

int ObjLoader::ComputeNormalInMesh(Mesh* a_mesh)
{
	fmath::vector3 v1, v2, normal;
	for(unsigned long i = 0; i < a_mesh->m_triangles.size(); ++i) {
		v1.x = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[0]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_vertexNum].m_position[0];
		v1.y = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[1]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_vertexNum].m_position[1];
		v1.z = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[2]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_vertexNum].m_position[2];

		v2.x = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_vertexNum].m_position[0]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[0];
		v2.y = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_vertexNum].m_position[1]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[1];
		v2.z = a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_vertexNum].m_position[2]
			- a_mesh->m_vertices[a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_vertexNum].m_position[2];

		normal = fmath::CrossProduct(v1, v2);

		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_normal[0] += normal.x;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_normal[1] += normal.y;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[0]].m_normal[2] += normal.z;

		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_normal[0] += normal.x;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_normal[1] += normal.y;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[1]].m_normal[2] += normal.z;

		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_normal[0] += normal.x;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_normal[1] += normal.y;
		a_mesh->m_normals[a_mesh->m_triangles[i].m_normals[2]].m_normal[2] += normal.z;
	}

	for(unsigned long i = 0; i < a_mesh->m_normals.size(); ++i) {
		normal.x = a_mesh->m_normals[i].m_normal[0];
		normal.y = a_mesh->m_normals[i].m_normal[1];
		normal.z = a_mesh->m_normals[i].m_normal[2];
		normal = fmath::Normalized(normal);
		a_mesh->m_normals[i].m_normal[0] = normal.x;
		a_mesh->m_normals[i].m_normal[1] = normal.y;
		a_mesh->m_normals[i].m_normal[2] = normal.z;
	}
	return 0;
}